package dkbta.ontology.values;

import dkbta.ontology.loading.OntologyException;
import dkbta.util.time.TimeUnit;
import dkbta.util.time.TimeUtils;

/**
 * A numeric range of double values. Can be fully open, fully closed or half-open from either side.<br>
 * <br>
 * Asterisk (*) can be used as a wild card in either side. In minimum it is replaced by
 * <tt>Double.NEGATIVE_INFINITY</tt> and in maximum by <tt>Double.POSITIVE_INFINITY</tt>.
 * 
 * @author Uri Kanonov
 */
public class NumericValues implements Values{
	/**
	 * Denotes whether equality may or may not be used when comparing the lower bound to a
	 * value
	 */
	protected final boolean _isMinE;

	/** The lower bound */
	protected final double _min;

	/**
	 * Denotes whether equality may or may not be used when comparing the upper bound to a
	 * value
	 */
	protected final boolean _isMaxE;

	/** The upper bound */
	protected final double _max;

	/**
	 * Constructs a NumericValues object with the given parameters
	 * 
	 * @param minE The lower bound with equality
	 * @param min The lower bound without equality
	 * @param maxE The upper bound with equality
	 * @param max The upper bound without equality
	 * @param timeUnit The time unit per which the values are (if any - i.e. if they
	 *        represent rates)
	 * @throws OntologyException
	 */
	public NumericValues(String minE, String min, String maxE, String max,
		TimeUnit timeUnit) throws OntologyException{
		_isMinE = min.isEmpty();
		_isMaxE = max.isEmpty();

		min = (min.isEmpty()) ? minE : min;
		max = (max.isEmpty()) ? maxE : max;

		// Setting the minimum and maximum
		try{
			_min = parseMinValue(min, timeUnit);
			_max = parseMaxValue(max, timeUnit);
		}catch(NumberFormatException e){
			throw new OntologyException("Illegal min or max value: [" + min + "," + max
					+ "]", e);
		}
		if (_min > _max){
			throw new OntologyException("Illegal min or max value: " + toString()
					+ ": min cannot be bigger than max");
		}
	}

	/**
	 * Returns the value of a number (represented by the string min)in the role of a lower
	 * bound
	 * 
	 * @param min The value of a number
	 * @param timeUnit The time unit per which the values are (if any - i.e. if they
	 *        represent rates)
	 * @return The value of a number (represented by the string min)in the role of a lower
	 *         bound
	 * @throws NumberFormatException If min doesn't represent a valid double
	 */
	protected double parseMinValue(String min, TimeUnit timeUnit)
			throws NumberFormatException{
		double value = "*".equals(min) ? Double.NEGATIVE_INFINITY : Double
				.parseDouble(min);
		return timeUnit == null ? value : TimeUtils.convertFromTimeUnitToMillis(value,
			timeUnit);
	}

	/**
	 * Returns the value of a number (represented by the string max) in the role of an
	 * upper bound
	 * 
	 * @param max The value of a number
	 * @param timeUnit The time unit per which the values are (if any - i.e. if they
	 *        represent rates)
	 * @return The value of a number (represented by the string max)in the role of an
	 *         upper bound
	 * @throws NumberFormatException If max doesn't represent a valid double
	 */
	protected double parseMaxValue(String max, TimeUnit timeUnit)
			throws NumberFormatException{
		double value = "*".equals(max) ? Double.POSITIVE_INFINITY : Double
				.parseDouble(max);
		return timeUnit == null ? value : TimeUtils.convertFromTimeUnitToMillis(value,
			timeUnit);
	}

	public ValuesType getValueType(){
		return ValuesType.NUMERIC;
	}

	@Override
	public String toString(){
		return (_isMinE ? "[" : "(") + _min + ", " + _max + (_isMaxE ? "]" : ")");
	}

	/**
	 * Checks whether the given value (in string form) fits into the possible values,
	 * parses it accordingly and returns it. Returns null if the value wasn't valid.
	 * 
	 * @param value The tested value
	 * @return The parsed value according to the values object or null if the value wasn't
	 *         valid.
	 */
	public Double parseAndValidate(String value){
		double numericValue;
		try{
			numericValue = Double.parseDouble(value);
			if (isValid(numericValue)){
				return numericValue;
			}
		}catch(NumberFormatException e){}
		return null;
	}

	/**
	 * Checks whether the given value (already parsed) fits into the possible values
	 * 
	 * @param valueType The type of the value
	 * @param value The value
	 * @return Whether the given value fits into the possible values
	 */
	public boolean isValid(ValuesType valueType, Object value){
		if (valueType == getValueType()){
			return isValid((Double)value);
		}else{
			return false;
		}
	}

	/**
	 * Checks whether the given numeric value is in the range specified by min, max,
	 * isMinE and isMaxE
	 * 
	 * @param numericValue The value
	 * @return Whether the given numeric value is in the range specified by min, max,
	 *         isMinE and isMaxE
	 */
	public boolean isValid(double numericValue){
		return ((_isMinE) ? numericValue >= _min : numericValue > _min)
				&& ((_isMaxE) ? numericValue <= _max : numericValue < _max);
	}

	@Override
	public boolean contains(Values v){
		return true; // Skipping the check for numeric values
	}
}
